<script setup>
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
import Palette from '../components/builder/Palette.vue'
import Canvas from '../components/builder/Canvas.vue'
import Inspector from '../components/builder/Inspector.vue'

const nodes = ref([])
const selectedNodeId = ref(null)
let autoId = 1

// Undo history state
const history = ref([])
const historyIndex = ref(-1)
const isRestoring = ref(false)

function createSnapshot() {
  return {
    nodes: JSON.parse(JSON.stringify(nodes.value)),
    selectedNodeId: selectedNodeId.value,
    autoId
  }
}

function snapshotsEqual(a, b) {
  if (!a || !b) return false
  return JSON.stringify(a.nodes) === JSON.stringify(b.nodes)
}

function pushHistory() {
  if (isRestoring.value) return
  const snap = createSnapshot()
  const last = history.value[historyIndex.value]
  if (snapshotsEqual(snap, last)) return
  if (historyIndex.value < history.value.length - 1) {
    history.value.splice(historyIndex.value + 1)
  }
  history.value.push(snap)
  if (history.value.length > 100) history.value.shift()
  historyIndex.value = history.value.length - 1
}

function restoreFromSnapshot(snap) {
  isRestoring.value = true
  nodes.value = JSON.parse(JSON.stringify(snap.nodes))
  selectedNodeId.value = snap.selectedNodeId
  autoId = snap.autoId
  isRestoring.value = false
}

// Export/Import helpers
function getExportData() {
  return {
    version: 1,
    autoId,
    nodes: JSON.parse(JSON.stringify(nodes.value))
  }
}

function downloadJson() {
  console.log('downloadJson')
  const data = getExportData()
  const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  const ts = new Date()
  const pad = (n) => String(n).padStart(2, '0')
  const filename = `canvas-${ts.getFullYear()}${pad(ts.getMonth()+1)}${pad(ts.getDate())}-${pad(ts.getHours())}${pad(ts.getMinutes())}${pad(ts.getSeconds())}.json`
  a.href = url
  a.download = filename
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  URL.revokeObjectURL(url)
}

const fileInputRef = ref(null)

function triggerImport() {
  if (fileInputRef.value) fileInputRef.value.value = ''
  fileInputRef.value?.click()
}

function sanitizeNodes(inputNodes) {
  if (!Array.isArray(inputNodes)) return []
  return inputNodes
    .filter(n => n && typeof n === 'object' && (n.type === 'container' || n.type === 'text'))
    .map(n => {
      if (n.type === 'container') {
        return {
          id: Number(n.id) || 0,
          type: 'container',
          x: Math.max(0, Math.round(Number(n.x) || 0)),
          y: Math.max(0, Math.round(Number(n.y) || 0)),
          width: Math.max(10, Math.round(Number(n.width) || 120)),
          height: Math.max(10, Math.round(Number(n.height) || 80)),
          backgroundColor: typeof n.backgroundColor === 'string' ? n.backgroundColor : '#f5f5f5',
          useBorder: Boolean(n.useBorder),
          borderColor: typeof n.borderColor === 'string' ? n.borderColor : '#333333',
          borderWidth: Math.max(0, Math.round(Number(n.borderWidth) || 2)),
          borderRadius: Math.max(0, Math.round(Number(n.borderRadius) || 8))
        }
      } else {
        return {
          id: Number(n.id) || 0,
          type: 'text',
          x: Math.max(0, Math.round(Number(n.x) || 0)),
          y: Math.max(0, Math.round(Number(n.y) || 0)),
          text: typeof n.text === 'string' ? n.text : '双击编辑文字',
          color: typeof n.color === 'string' ? n.color : '#222222',
          fontSize: Math.max(8, Math.round(Number(n.fontSize) || 16))
        }
      }
    })
}

async function onFileChange(e) {
  const file = e.target?.files && e.target.files[0]
  if (!file) return
  try {
    const text = await file.text()
    const data = JSON.parse(text)
    const importedNodes = sanitizeNodes(data?.nodes)
    const nextAutoId = Number(data?.autoId)
    const maxId = importedNodes.reduce((m, n) => Math.max(m, Number(n.id) || 0), 0)
    const snap = {
      nodes: importedNodes,
      selectedNodeId: null,
      autoId: Number.isFinite(nextAutoId) && nextAutoId > maxId ? nextAutoId : maxId + 1
    }
    restoreFromSnapshot(snap)
    // reset history to the imported state
    history.value = []
    historyIndex.value = -1
    pushHistory()
  } catch (err) {
    console.error(err)
  }
}

function undo() {
  if (historyIndex.value <= 0) return
  historyIndex.value -= 1
  const snap = history.value[historyIndex.value]
  if (snap) restoreFromSnapshot(snap)
}

function onKeydown(e) {
  const t = e.target
  const isEditable = t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)
  if (isEditable) return
  if (e.ctrlKey && !e.shiftKey && e.key.toLowerCase() === 'z') {
    e.preventDefault()
    undo()
  }
}

const selectedNode = computed(() => nodes.value.find(n => n.id === selectedNodeId.value) || null)

function handleDropNew({ type, x, y }) {
  const id = autoId++
  if (type === 'container') {
    nodes.value.push({
      id,
      type,
      x: Math.max(0, Math.round(x - 60)),
      y: Math.max(0, Math.round(y - 40)),
      width: 120,
      height: 80,
      backgroundColor: '#f5f5f5',
      useBorder: true,
      borderColor: '#333333',
      borderWidth: 2,
      borderRadius: 8
    })
  } else if (type === 'text') {
    nodes.value.push({
      id,
      type,
      x: Math.max(0, Math.round(x - 40)),
      y: Math.max(0, Math.round(y - 12)),
      text: '双击编辑文字',
      color: '#222222',
      fontSize: 16
    })
  }
  selectedNodeId.value = id
}

function handleMoveNode({ id, x, y }) {
  const node = nodes.value.find(n => n.id === id)
  if (!node) return
  node.x = Math.round(x)
  node.y = Math.round(y)
}

function handleSelectNode(id) {
  selectedNodeId.value = id
}

onMounted(() => {
  pushHistory()
  window.addEventListener('keydown', onKeydown)
})

onBeforeUnmount(() => {
  window.removeEventListener('keydown', onKeydown)
})

// Deep watch nodes to record changes (drops, moves, inspector edits)
watch(nodes, () => {
  pushHistory()
}, { deep: true })

// Settings menu state
const isSettingsOpen = ref(false)
function toggleSettings() {
  isSettingsOpen.value = !isSettingsOpen.value
}
function closeSettings() {
  isSettingsOpen.value = false
}

</script>

<template>
  <div class="builder-root">
    <input ref="fileInputRef" type="file" accept="application/json,.json" @change="onFileChange" style="display:none" />
    <Palette />
    <Canvas
      :nodes="nodes"
      :selectedNodeId="selectedNodeId"
      @dropNew="handleDropNew"
      @moveNode="handleMoveNode"
      @selectNode="handleSelectNode"
    />
    <Inspector :selectedNode="selectedNode" />

    <!-- Settings floating button and menu at bottom-left -->
    <div class="settings-root">
      <button class="settings-btn" @click="toggleSettings" aria-label="设置">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <circle cx="12" cy="12" r="3" />
          <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9c0 .66.39 1.26 1 1.51H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
        </svg>
      </button>

      <div v-if="isSettingsOpen" class="settings-menu">
        <button class="menu-item" @click="downloadJson(); closeSettings()">导出 JSON</button>
        <button class="menu-item" @click="triggerImport(); closeSettings()">导入 JSON</button>
      </div>

      <div v-if="isSettingsOpen" class="settings-overlay" @click="closeSettings"></div>
    </div>
  </div>
  
</template>

<style scoped>
.builder-root {
  display: grid;
  grid-template-columns: 280px 1fr 280px;
  height: 100vh;
}

.settings-root {
  position: fixed;
  left: 16px;
  bottom: 16px;
  z-index: 20;
}

.settings-btn {
  width: 38px;
  height: 38px;
  border-radius: 999px;
  border: 1px solid #e5e7eb;
  background: #ffffff;
  box-shadow: 0 2px 6px rgba(0,0,0,0.12);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.settings-menu {
  position: absolute;
  left: 0;
  bottom: 46px;
  background: #ffffff;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
  padding: 6px;
  min-width: 140px;
  z-index: 20;
}

.menu-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 10px;
  background: transparent;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}

.menu-item:hover {
  background: #f5f5f5;
}

.settings-overlay {
  position: fixed;
  inset: 0;
  background: transparent;
  z-index: 10;
}
</style>


